package com.hotec.PROJECT_NAME.utils;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hotec.PROJECT_NAME.component.exception.exc.BizException;
import com.hotec.PROJECT_NAME.config.encrypt.EncryptConfig;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Utils3DES {
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private static final String JKS_PATH = "demo.jks";

    private static final File jksFile = UtilsFile.getFileInClassPath(JKS_PATH);

    public static <T> T decrypt(byte[] requestBody, TypeReference<T> typeReference){
        EncryptConfig encryptConfig = getEncryptConfig();

        try {
            byte[] keyBytes = decryptRequestKey(Base64Decoder.decode(getRequestAk()));

            String data = new String(requestBody, DEFAULT_CHARSET).replaceAll("\"", "");

            byte[] decrypted = decryptRequestBody(keyBytes, encryptConfig.getIv(), Base64Decoder.decode(data));

            return convertDecryptResult(decrypted, typeReference);
        } catch (Exception e) {
            UtilsLog.log(Utils3DES.class, e);

            throw new BizException("非法请求");
        }
    }

    public static <T> T decryptWithStr(String requestBody, TypeReference<T> typeReference){
        return commonDecryptHandler(requestBody, decrypted -> convertDecryptResult(decrypted, typeReference));
    }

    public static <T> T decryptWithStr(String requestBody, Class<T> type){
        return commonDecryptHandler(requestBody, decrypted -> convertDecryptResult(decrypted, type));
    }

    private static <T> T commonDecryptHandler(String requestBody, Function<byte[], T> handler) {
        EncryptConfig encryptConfig = getEncryptConfig();

        try {
            byte[] keyBytes = decryptRequestKey(Base64Decoder.decode(getRequestAk()));

            byte[] decrypted = decryptRequestBody(keyBytes, encryptConfig.getIv(), Base64Decoder.decode(requestBody));

            return handler.apply(decrypted);
        } catch (Exception e) {
            UtilsLog.log(Utils3DES.class, e);

            throw new BizException("非法请求");
        }
    }

    public static <T> T decryptWithRequest(HttpServletRequest request, TypeReference<T> typeReference) throws IOException {
        String requestBody = request.getReader().lines().collect(Collectors.joining()).replaceAll("\"", "");

        return decryptWithStr(requestBody, typeReference);
    }

    public static <T> T decryptWithRequest(HttpServletRequest request, Class<T> type) throws IOException {
        String requestBody = request.getReader().lines().collect(Collectors.joining()).replaceAll("\"", "");

        return decryptWithStr(requestBody, type);
    }

    private static <T> T convertDecryptResult(byte[] decrypted, TypeReference<T> typeReference) {
        Object readValue = null;
        try {
            readValue = new ObjectMapper().readValue(decrypted, Object.class);
        } catch (IOException e) {
            UtilsLog.log(Utils3DES.class, e);

            throw new BizException(e.getMessage());
        }

        return Convert.convert(typeReference.getType(), readValue);
    }

    private static <T> T convertDecryptResult(byte[] decrypted, Class<T> type) {
        Object readValue = null;
        try {
            readValue = new ObjectMapper().readValue(decrypted, Object.class);
        } catch (IOException e) {
            UtilsLog.log(Utils3DES.class, e);

            throw new BizException(e.getMessage());
        }

        return Convert.convert(type, readValue);
    }

    public static String encrypt(String content) throws Exception{
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        Cipher cipher = Cipher.getInstance("desede/CBC/PKCS7Padding");

        SecretKeySpec keySpec = new SecretKeySpec(getRequestAkWithDecoder().getBytes(DEFAULT_CHARSET), "desede");

        AlgorithmParameterSpec paramSpec = new IvParameterSpec(getEncryptConfig().getIv());

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec);
        return Base64Encoder.encode(cipher.doFinal(content.getBytes(DEFAULT_CHARSET))).replaceAll(System.lineSeparator(), "");
    }

    private static EncryptConfig getEncryptConfig (){
        return Assert.notNull(UtilsSpring.getBean(EncryptConfig.class), "未能正确加载请求解密配置，请检查！");
    }

    public static byte[] decryptRequestKey (char[] password, String alias, byte[] ak) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("JKS");

        keyStore.load(new FileInputStream(jksFile), password);

        Cipher cipher = Cipher.getInstance("RSA");

        cipher.init(Cipher.DECRYPT_MODE, keyStore.getKey(alias, password));

        return cipher.doFinal(ak);
    }

    private static byte[] decryptRequestBody (byte[] key, byte[] iv, byte[] requestBody) throws Exception{
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        Cipher cipher = Cipher.getInstance("desede/CBC/PKCS7Padding");

        SecretKeySpec keySpec = new SecretKeySpec(key, "desede");

        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);

        cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);

        int blockSize = cipher.getBlockSize();

        int plaintextLength = requestBody.length;

        if (plaintextLength % blockSize != 0) {
            plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
        }

        byte[] plaintext = new byte[plaintextLength];

        System.arraycopy(requestBody, 0, plaintext, 0, requestBody.length);

        return cipher.doFinal(plaintext);
    }

    private static byte[] decryptRequestKey (byte[] ak) throws Exception {
        EncryptConfig encryptConfig = getEncryptConfig();

        KeyStore keyStore = KeyStore.getInstance("JKS");

        keyStore.load(new FileInputStream(jksFile), encryptConfig.getPassword());

        Cipher cipher = Cipher.getInstance("RSA");

        cipher.init(Cipher.DECRYPT_MODE, keyStore.getKey(encryptConfig.getAlias(), encryptConfig.getPassword()));

        return cipher.doFinal(ak);
    }

    public static String getRequestAk () {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        return Assert.notBlank(request.getHeader("a-k-l-t"), "非法请求");
    }

    public static String getRequestAkWithDecoder () throws Exception {
        EncryptConfig encryptConfig = getEncryptConfig();

        byte[] bytes = decryptRequestKey(encryptConfig.getPassword(), encryptConfig.getAlias(), Base64Decoder.decode(getRequestAk()));

        return new String(bytes, DEFAULT_CHARSET);
    }
}
